#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>

MODULE_LICENSE("GPL");

#define MODULENAME "registerchrdev"

#define LOG_INFO(fmt, ...) \
    printk(KERN_INFO MODULENAME ": " fmt, ##__VA_ARGS__)

#define LOG_ERR(fmt, ...) \
    printk(KERN_ERR MODULENAME ": " fmt, ##__VA_ARGS__)

#define MINOR_NUM_COUNT 1
static int major_num = 0, minor_num = 0;
struct class *cls = NULL;

// open
static int chrdev_open(struct inode *inode,
                       struct file *filp)
{
    LOG_INFO("open");
    return 0;
}

// release
static int chrdev_release(struct inode *inode,
                          struct file *filp)
{
    LOG_INFO("release");
    return 0;
}

struct cdev hellocdev = {
    .owner = THIS_MODULE,
};

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = chrdev_open,
};

static int __init registerchrdev_init(void)
{
    LOG_INFO("Hello, registerchrdev\n");
    if (major_num == 0)
    {
        int ret = alloc_chrdev_region(&hellocdev.dev, 0, MINOR_NUM_COUNT, MODULENAME);
        if (ret < 0)
        {
            LOG_ERR("alloc_chrdev_region failed\n");
            return ret;
        }
        else
        {
            major_num = MAJOR(hellocdev.dev);
            minor_num = MINOR(hellocdev.dev);
            LOG_INFO("alloc_chrdev_region success\n");
            LOG_INFO("major_num = %d, minor_num = %d\n", MAJOR(hellocdev.dev), MINOR(hellocdev.dev));
        }
    }

    { // 初始化字符设备
        cdev_init(&hellocdev, &fops);
        cdev_add(&hellocdev, MKDEV(major_num, minor_num), MINOR_NUM_COUNT);
    }

    { // 创建设备文件
        // 在/sys/class/目录下创建一个类，类名为就是第二个参数
        cls = class_create(hellocdev.owner, MODULENAME);

        // 在/dev/目录下创建设备文件,文件名为最后一个参数
        device_create(cls,
                      NULL,
                      MKDEV(major_num, minor_num),
                      NULL,
                      MODULENAME);
    }
    return 0;
}

static void registerchrdev_exit(void)
{
    { // 删除设备文件
        device_destroy(cls, MKDEV(major_num, minor_num));
        class_destroy(cls);
    }

    { // 删除字符设备
        cdev_del(&hellocdev);
    }

    { // 注销设备号
        unregister_chrdev_region(MKDEV(major_num, minor_num), MINOR_NUM_COUNT);
    }

    LOG_INFO("Goodbye, registerchrdev\n");
}

module_init(registerchrdev_init);
module_exit(registerchrdev_exit);
